22.3 OOP and Inheritance: "is-a" Relationships
We've talked about the mechanics of
inheritance in depth already,
but we'd like to show you an
example of how
it can be used to model real-world relationships. From a
programmer's point of view, inheritance is kicked
off by attribute qualifications, and triggers a search for a name in
an instance, its class, and then its superclasses. From a
designer's point of view, inheritance is a way to
specify set membership: a class defines a set of properties that may
be inherited and customized by more specific sets (i.e., subclasses).
To illustrate, let's put that pizza-making robot we
talked about at the start of this part of the book to work. Suppose
we've decided to explore alternative career paths
and open a pizza restaurant. One of the first things
we'll need to do is hire employees to serve
customers, make the pizza, and so on. Being engineers at heart,
we've also decided to build a robot to make the
pizzas; but being politically and cybernetically correct,
we've also decided to make our robot a full-fledged
employee, with a salary.
Our pizza shop team can be defined by the following classes in the
example file employees.py. It defines four
classes and some self-test code. The most general class,
Employee, provides common behavior such as bumping
up salaries (giveRaise) and printing
(_ _repr__). There are two
kinds of employees, and so two subclasses of
Employee—Chef and
Server. Both override the inherited
work method to print more specific messages.
Finally, our pizza robot is modeled by an even more specific class:
PizzaRobot is a kind of Chef,
which is a kind of Employee. In OOP terms, we call
these relationships "is-a" links: a
robot is a chef, which is a(n) employee.
class Employee:
def __init__(self, name, salary=0):
self.name = name
self.salary = salary
def giveRaise(self, percent):
self.salary = self.salary + (self.salary * percent)
def work(self):
print self.name, "does stuff"
def __repr__(self):
return "<Employee: name=%s, salary=%s>" % (self.name, self.salary)
class Chef(Employee):
def __init__(self, name):
Employee.__init__(self, name, 50000)
def work(self):
print self.name, "makes food"
class Server(Employee):
def __init__(self, name):
Employee.__init__(self, name, 40000)
def work(self):
print self.name, "interfaces with customer"
class PizzaRobot(Chef):
def __init__(self, name):
Chef.__init__(self, name)
def work(self):
print self.name, "makes pizza"
if __name__ == "__main__":
bob = PizzaRobot('bob') # Make a robot named bob.
print bob # Runs inherited __repr__
bob.work( ) # Run type-specific action.
bob.giveRaise(0.20) # Give bob a 20% raise.
print bob; print
for klass in Employee, Chef, Server, PizzaRobot:
obj = klass(klass.__name__)
obj.work( )
When we run this module's self-test code, we create
a pizza-making robot named bob, which inherits
names from three classes: PizzaRobot,
Chef, and Employee. For
instance, printing bob runs the
Employee.__repr__ method, and giving
bob a raise invokes
Employee.giveRaise, because
that's where inheritance finds it.
C:\python\examples> python employees.py
<Employee: name=bob, salary=50000>
bob makes pizza
<Employee: name=bob, salary=60000.0>
Employee does stuff
Chef makes food
Server interfaces with customer
PizzaRobot makes pizza
In a class hierarchy like this, you can usually make instances of any
of the classes, not just the ones at the bottom. For instance, the
for loop in this module's
self-test code creates instances of all four classes; each responds
differently when asked to work, because the work
method is different in each. Really, these classes just simulate real
world objects; work prints a message for the time
being, but could be expanded to really work later.
|